﻿'This class replaces the NAudio implementation of AudioFileReader, by implementing custom LoopingReaders in Q2Cue.


Imports NAudio.Wave.SampleProviders
Imports NAudio.Wave



Namespace Audio

	Public Enum AudioFileReaders
		Aiff
		MediaFoundation
		Mp3
		Wave
	End Enum


	''' <summary>
	''' AudioFileReader simplifies opening an audio file in NAudio
	''' Simply pass in the filename, and it will attempt to open the
	''' file and set up a conversion path that turns into PCM IEEE float.
	''' ACM codecs will be used for conversion.
	''' It provides a volume property and implements both WaveStream and
	''' ISampleProvider, making it possibly the only stage in your audio
	''' pipeline necessary for simple playback scenarios
	''' </summary>
	Public Class LoopingAudioFileReader
		Inherits WaveStream
		Implements ISampleProvider
		Private fileName As String
		Private readerStream As WaveStream
		' the waveStream which we will use for all positioning
		Private ReadOnly sampleChannel As SampleChannel
		' sample provider that gives us most stuff we need
		Private ReadOnly destBytesPerSample As Integer
		Private ReadOnly sourceBytesPerSample As Integer
		Private ReadOnly m_length As Long
		Private ReadOnly lockObject As Object

		Public Event DoRead(sender As Object, e As EventArgs)
		Private Sub PropogateDoRead(sender As Object, e As EventArgs)
			RaiseEvent DoRead(sender, e)
		End Sub

		Private ReaderType As AudioFileReaders = AudioFileReaders.Wave

		''' <summary>
		''' When set to true the reader will loop through the audio file repeatedly.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property EnableLooping As Boolean
			Get
				If readerStream IsNot Nothing Then
					If readerStream.GetType Is GetType(LoopingWaveFileReader) Then
						Return CType(readerStream, LoopingWaveFileReader).EnableLooping
					ElseIf readerStream.GetType Is GetType(LoopingAiffFileReader) Then
						Return CType(readerStream, LoopingAiffFileReader).EnableLooping
					ElseIf readerStream.GetType Is GetType(LoopingMp3FileReader) Then
						Return CType(readerStream, LoopingMp3FileReader).EnableLooping
					ElseIf readerStream.GetType Is GetType(LoopingMediaFoundationReader) Then
						Return CType(readerStream, LoopingMediaFoundationReader).EnableLooping
					Else
						Return False
					End If
				Else
					Return False
				End If
			End Get
			Set(value As Boolean)
				If readerStream IsNot Nothing Then
					If readerStream.GetType Is GetType(LoopingWaveFileReader) Then
						CType(readerStream, LoopingWaveFileReader).EnableLooping = value
					ElseIf readerStream.GetType Is GetType(LoopingAiffFileReader) Then
						CType(readerStream, LoopingAiffFileReader).EnableLooping = value
					ElseIf readerStream.GetType Is GetType(LoopingMp3FileReader) Then
						CType(readerStream, LoopingMp3FileReader).EnableLooping = value
					ElseIf readerStream.GetType Is GetType(LoopingMediaFoundationReader) Then
						CType(readerStream, LoopingMediaFoundationReader).EnableLooping = value
					End If
				End If
			End Set
		End Property

		''' <summary>
		''' The time in the audio file the reader should start at.
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property StartTime As TimeSpan
			Get
				If readerStream IsNot Nothing Then
					If readerStream.GetType Is GetType(LoopingWaveFileReader) Then
						Return CType(readerStream, LoopingWaveFileReader).StartTime
					ElseIf readerStream.GetType Is GetType(LoopingAiffFileReader) Then
						Return CType(readerStream, LoopingAiffFileReader).StartTime
					ElseIf readerStream.GetType Is GetType(LoopingMp3FileReader) Then
						Return CType(readerStream, LoopingMp3FileReader).StartTime
					ElseIf readerStream.GetType Is GetType(LoopingMediaFoundationReader) Then
						Return CType(readerStream, LoopingMediaFoundationReader).StartTime
					Else
						Return New TimeSpan(0)
					End If
				Else
					Return New TimeSpan(0)
				End If
			End Get
			Set(value As TimeSpan)
				If readerStream IsNot Nothing Then
					If readerStream.GetType Is GetType(LoopingWaveFileReader) Then
						CType(readerStream, LoopingWaveFileReader).StartTime = value
					ElseIf readerStream.GetType Is GetType(LoopingAiffFileReader) Then
						CType(readerStream, LoopingAiffFileReader).StartTime = value
					ElseIf readerStream.GetType Is GetType(LoopingMp3FileReader) Then
						CType(readerStream, LoopingMp3FileReader).StartTime = value
					ElseIf readerStream.GetType Is GetType(LoopingMediaFoundationReader) Then
						CType(readerStream, LoopingMediaFoundationReader).StartTime = value
					End If
				End If
			End Set
		End Property



		''' <summary>
		''' Initializes a new instance of AudioFileReader
		''' </summary>
		''' <param name="fileName">The file to open</param>
		Public Sub New(fileName As String)
			lockObject = New Object()
			Me.fileName = fileName


			If My.Application.Show.ShowFile.IsPackage Then
				If fileName.EndsWith(".wav") Then
					ReaderType = AudioFileReaders.Wave
				ElseIf fileName.EndsWith(".aif") OrElse fileName.EndsWith(".aiff") Then
					ReaderType = AudioFileReaders.Aiff
				ElseIf fileName.EndsWith(".mp3") Then
					ReaderType = AudioFileReaders.Mp3
				Else
					ReaderType = AudioFileReaders.MediaFoundation
				End If
				Dim stream As New IO.MemoryStream()

                Using pack As System.IO.Packaging.ZipPackage = System.IO.Packaging.ZipPackage.Open(My.Application.Show.ShowFile.Path & "\" & My.Application.Show.ShowFile.Name, IO.FileMode.Open)
                    Dim partUri As Uri = Shows.Archiving.GetAudioUri(fileName)
                    If pack.PartExists(partUri) Then
                        Dim part As IO.Packaging.ZipPackagePart = pack.GetPart(Shows.Archiving.GetAudioUri(fileName))
                        If part IsNot Nothing Then
                            Q2Q.Shows.Archiving.CopyStream(part.GetStream, stream)
                            If ReaderType = AudioFileReaders.Aiff OrElse ReaderType = AudioFileReaders.Wave OrElse ReaderType = AudioFileReaders.Mp3 Then
                                stream.Position = 0
                                CreateReaderStream(stream)
                            Else
                                'Since the media foundation can't create from streams, write the file to a temporary audio location.
                                'Ultimately we should change this so that a temporary file isn't needed. The best way to do this will be:
                                'TODO: Implement the entirety of the NAudio MediaFoundationReader class inside the LoopingMediaFoundationReader class, instead of inheriting from it, so that streams can be passed in.
                                Try
                                    If Not FileIO.FileSystem.DirectoryExists(My.Application.Show.ShowFile.Path & "\q2qAudio.Temp") Then
                                        FileIO.FileSystem.CreateDirectory(My.Application.Show.ShowFile.Path & "\q2qAudio.Temp")
                                    End If
                                    stream.WriteTo(New IO.FileStream(My.Application.Show.ShowFile.Path & "\q2qAudio.Temp\" & FileIO.FileSystem.GetName(fileName), IO.FileMode.OpenOrCreate))
                                Catch
                                End Try
                                CreateReaderStream(My.Application.Show.ShowFile.Path & "\q2qAudio.Temp\" & FileIO.FileSystem.GetName(fileName))
                            End If
                        Else
                            CreateReaderStream(fileName)
                        End If
                    Else
                        CreateReaderStream(fileName)
                    End If
                End Using
            Else
				CreateReaderStream(fileName)
			End If





				sourceBytesPerSample = (readerStream.WaveFormat.BitsPerSample / 8) * readerStream.WaveFormat.Channels
				sampleChannel = New SampleChannel(readerStream, False)
				destBytesPerSample = 4 * sampleChannel.WaveFormat.Channels
				m_length = SourceToDest(readerStream.Length)
		End Sub

		''' <summary>
		''' Creates the reader stream, supporting all filetypes in the core NAudio library,
		''' and ensuring we are in PCM format
		''' </summary>
		''' <param name="fileName">File Name</param>
		Private Sub CreateReaderStream(fileName As String)
			If fileName.EndsWith(".wav", StringComparison.OrdinalIgnoreCase) Then
				ReaderType = AudioFileReaders.Wave
				readerStream = New LoopingWaveFileReader(fileName)
				AddHandler CType(readerStream, LoopingWaveFileReader).DoRead, AddressOf PropogateDoRead
				If readerStream.WaveFormat.Encoding <> WaveFormatEncoding.Pcm AndAlso readerStream.WaveFormat.Encoding <> WaveFormatEncoding.IeeeFloat Then
					readerStream = WaveFormatConversionStream.CreatePcmStream(readerStream)
					readerStream = New BlockAlignReductionStream(readerStream)
				End If
			ElseIf fileName.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase) Then
				ReaderType = AudioFileReaders.Mp3
				readerStream = New LoopingMp3FileReader(fileName)
				AddHandler CType(readerStream, LoopingMp3FileReader).DoRead, AddressOf PropogateDoRead
			ElseIf fileName.EndsWith(".aiff") OrElse fileName.EndsWith(".aif") Then
				ReaderType = AudioFileReaders.Aiff
				readerStream = New LoopingAiffFileReader(fileName)
				AddHandler CType(readerStream, LoopingAiffFileReader).DoRead, AddressOf PropogateDoRead
			Else
				' fall back to media foundation reader, see if that can play it
				ReaderType = AudioFileReaders.MediaFoundation
				readerStream = New LoopingMediaFoundationReader(fileName)
				AddHandler CType(readerStream, LoopingMediaFoundationReader).DoRead, AddressOf PropogateDoRead
			End If
		End Sub

		''' <summary>
		''' Creates the reader stream, supporting all filetypes in the core NAudio library,
		''' and ensuring we are in PCM format
		''' </summary>
		''' <param name="fileStream">A Stream of the File (for reading from archives)</param>
		Private Sub CreateReaderStream(fileStream As System.IO.Stream)
			If ReaderType = AudioFileReaders.Wave Then
				readerStream = New LoopingWaveFileReader(fileStream)
				AddHandler CType(readerStream, LoopingWaveFileReader).DoRead, AddressOf PropogateDoRead
				If readerStream.WaveFormat.Encoding <> WaveFormatEncoding.Pcm AndAlso readerStream.WaveFormat.Encoding <> WaveFormatEncoding.IeeeFloat Then
					readerStream = WaveFormatConversionStream.CreatePcmStream(readerStream)
					readerStream = New BlockAlignReductionStream(readerStream)
				End If
			ElseIf ReaderType = AudioFileReaders.Mp3 Then
				readerStream = New LoopingMp3FileReader(fileStream)
				AddHandler CType(readerStream, LoopingMp3FileReader).DoRead, AddressOf PropogateDoRead
			ElseIf ReaderType = AudioFileReaders.Aiff Then
				readerStream = New LoopingAiffFileReader(fileStream)
				AddHandler CType(readerStream, LoopingAiffFileReader).DoRead, AddressOf PropogateDoRead
			Else
				' fall back to media foundation reader, see if that can play it
				'readerStream = New LoopingMediaFoundationReader(Nothing, fileStream)
				'readerStream = New LoopingMediaFoundationReader()
				'AddHandler CType(readerStream, LoopingMediaFoundationReader).DoRead, AddressOf PropogateDoRead
			End If
		End Sub

		''' <summary>
		''' WaveFormat of this stream
		''' </summary>
		Public Overrides ReadOnly Property WaveFormat() As WaveFormat Implements ISampleProvider.WaveFormat
			Get
				Return sampleChannel.WaveFormat
			End Get
		End Property

		''' <summary>
		''' Length of this stream (in bytes)
		''' </summary>
		Public Overrides ReadOnly Property Length() As Long
			Get
				Return m_length
			End Get
		End Property

		''' <summary>
		''' Position of this stream (in bytes)
		''' </summary>
		Public Overrides Property Position() As Long
			Get
				Return SourceToDest(readerStream.Position)
			End Get
			Set(value As Long)
				SyncLock lockObject
					readerStream.Position = DestToSource(value)
				End SyncLock
			End Set
		End Property

		''' <summary>
		''' Reads from this wave stream
		''' </summary>
		''' <param name="buffer">Audio buffer</param>
		''' <param name="offset">Offset into buffer</param>
		''' <param name="count">Number of bytes required</param>
		''' <returns>Number of bytes read</returns>
		Public Overrides Function Read(buffer As Byte(), offset As Integer, count As Integer) As Integer
			Dim waveBuffer = New WaveBuffer(buffer)
			Dim samplesRequired As Integer = count / 4
			Dim samplesRead As Integer = Read(waveBuffer.FloatBuffer, offset / 4, samplesRequired)
			Return samplesRead * 4
		End Function

		''' <summary>
		''' Reads audio from this sample provider
		''' </summary>
		''' <param name="buffer">Sample buffer</param>
		''' <param name="offset">Offset into sample buffer</param>
		''' <param name="count">Number of samples required</param>
		''' <returns>Number of samples read</returns>
		Public Overloads Function Read(buffer As Single(), offset As Integer, count As Integer) As Integer Implements NAudio.Wave.ISampleProvider.Read
			SyncLock lockObject
				Return sampleChannel.Read(buffer, offset, count)
			End SyncLock
		End Function

		''' <summary>
		''' Gets or Sets the Volume of this AudioFileReader. 1.0f is full volume
		''' </summary>
		Public Property Volume() As Single
			Get
				Return sampleChannel.Volume
			End Get
			Set(value As Single)
				sampleChannel.Volume = value
			End Set
		End Property

		''' <summary>
		''' Helper to convert source to dest bytes
		''' </summary>
		Private Function SourceToDest(sourceBytes As Long) As Long
			Return destBytesPerSample * (sourceBytes \ sourceBytesPerSample)
		End Function

		''' <summary>
		''' Helper to convert dest to source bytes
		''' </summary>
		Private Function DestToSource(destBytes As Long) As Long
			Return sourceBytesPerSample * (destBytes \ destBytesPerSample)
		End Function

		''' <summary>
		''' Disposes this AudioFileReader
		''' </summary>
		''' <param name="disposing">True if called from Dispose</param>
		Protected Overrides Sub Dispose(disposing As Boolean)
			If disposing Then
				If readerStream IsNot Nothing Then
					Try
						If ReaderType = AudioFileReaders.Wave Then
							RemoveHandler CType(readerStream, LoopingWaveFileReader).DoRead, AddressOf PropogateDoRead
						ElseIf ReaderType = AudioFileReaders.Mp3 Then
							RemoveHandler CType(readerStream, LoopingAiffFileReader).DoRead, AddressOf PropogateDoRead
						ElseIf ReaderType = AudioFileReaders.Aiff Then
							RemoveHandler CType(readerStream, LoopingMp3FileReader).DoRead, AddressOf PropogateDoRead
						ElseIf ReaderType = AudioFileReaders.MediaFoundation Then
							RemoveHandler CType(readerStream, LoopingMediaFoundationReader).DoRead, AddressOf PropogateDoRead
						End If
					Catch
					End Try
					Try
						readerStream.Dispose()
						readerStream = Nothing
					Catch
					End Try
				End If
			End If
			MyBase.Dispose(disposing)
		End Sub
	End Class
End Namespace